﻿using System;

namespace ASPItalia.ModelVirtualCasting.Random
{
    internal class IsaacRandomNumberGenerator : RandomNumberGeneratorBase
    {
        private const int SIZEL = 8;
        private const int SIZE = 1 << SIZEL;
        private const int MASK = (SIZE - 1) << 2;

        private int _index;
        private int[] _results;
        private int[] _state;
        private int _accumulator;
        private int _lastResult;
        private int _counter;

        public IsaacRandomNumberGenerator()
        {
            _state = new int[SIZE];
            _results = new int[SIZE];
            this.Initialize(false);
        }

        public IsaacRandomNumberGenerator(int[] seed)
        {
            _state = new int[SIZE];
            _results = new int[SIZE];

            for (int i = 0; i < seed.Length; ++i)
                _results[i] = seed[i];
            
            this.Initialize(true);
        }

        private void PerformIsaac()
        {
            int i, j, x, y;

            _lastResult += ++_counter;
            for (i = 0, j = SIZE / 2; i < SIZE / 2; )
            {
                x = _state[i];
                _accumulator ^= _accumulator << 13;
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;

                x = _state[i];
                _accumulator ^= (int)((uint)_accumulator >> 6);
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;

                x = _state[i];
                _accumulator ^= _accumulator << 2;
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;

                x = _state[i];
                _accumulator ^= (int)((uint)_accumulator >> 16);
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;
            }

            for (j = 0; j < SIZE / 2; )
            {
                x = _state[i];
                _accumulator ^= _accumulator << 13;
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;

                x = _state[i];
                _accumulator ^= (int)((uint)_accumulator >> 6);
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;

                x = _state[i];
                _accumulator ^= _accumulator << 2;
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;

                x = _state[i];
                _accumulator ^= (int)((uint)_accumulator >> 16);
                _accumulator += _state[j++];
                _state[i] = y = _state[(x & MASK) >> 2] + _accumulator + _lastResult;
                _results[i++] = _lastResult = _state[((y >> SIZEL) & MASK) >> 2] + x;
            }
        }

        private void Initialize(bool flag)
        {
            int i;
            int a, b, c, d, e, f, g, h;
            a = b = c = d = e = f = g = h = unchecked((int)0x9e3779b9);

            for (i = 0; i < 4; ++i)
            {
                a ^= b << 11; d += a; b += c;
                b ^= (int)((uint)c >> 2); e += b; c += d;
                c ^= d << 8; f += c; d += e;
                d ^= (int)((uint)e >> 16); g += d; e += f;
                e ^= f << 10; h += e; f += g;
                f ^= (int)((uint)g >> 4); a += f; g += h;
                g ^= h << 8; b += g; h += a;
                h ^= (int)((uint)a >> 9); c += h; a += b;
            }

            for (i = 0; i < SIZE; i += 8)
            {
                if (flag)
                {
                    a += _results[i]; b += _results[i + 1]; c += _results[i + 2]; d += _results[i + 3];
                    e += _results[i + 4]; f += _results[i + 5]; g += _results[i + 6]; h += _results[i + 7];
                }
                a ^= b << 11; d += a; b += c;
                b ^= (int)((uint)c >> 2); e += b; c += d;
                c ^= d << 8; f += c; d += e;
                d ^= (int)((uint)e >> 16); g += d; e += f;
                e ^= f << 10; h += e; f += g;
                f ^= (int)((uint)g >> 4); a += f; g += h;
                g ^= h << 8; b += g; h += a;
                h ^= (int)((uint)a >> 9); c += h; a += b;
                _state[i] = a; _state[i + 1] = b; _state[i + 2] = c; _state[i + 3] = d;
                _state[i + 4] = e; _state[i + 5] = f; _state[i + 6] = g; _state[i + 7] = h;
            }

            if (flag)
            {           
                for (i = 0; i < SIZE; i += 8)
                {
                    a += _state[i]; b += _state[i + 1]; c += _state[i + 2]; d += _state[i + 3];
                    e += _state[i + 4]; f += _state[i + 5]; g += _state[i + 6]; h += _state[i + 7];
                    a ^= b << 11; d += a; b += c;
                    b ^= (int)((uint)c >> 2); e += b; c += d;
                    c ^= d << 8; f += c; d += e;
                    d ^= (int)((uint)e >> 16); g += d; e += f;
                    e ^= f << 10; h += e; f += g;
                    f ^= (int)((uint)g >> 4); a += f; g += h;
                    g ^= h << 8; b += g; h += a;
                    h ^= (int)((uint)a >> 9); c += h; a += b;
                    _state[i] = a; _state[i + 1] = b; _state[i + 2] = c; _state[i + 3] = d;
                    _state[i + 4] = e; _state[i + 5] = f; _state[i + 6] = g; _state[i + 7] = h;
                }
            }

            this.PerformIsaac();
            _index = SIZE;
        }

        private int GetRandomValue()
        {
            if (0 == _index--)
            {
                this.PerformIsaac();
                _index = SIZE - 1;
            }

            return _results[_index];
        }

        protected override int GenerateSingleNumber(int lowerBound, int upperBound)
        {
            int randomNumber = -1;
            double randomValue = this.GenerateSingleDouble();

            int range = upperBound - lowerBound + 1;
            randomNumber = ((int)Math.Truncate(randomValue * (double)range)) + lowerBound;

            return randomNumber;
        }

        protected override double GenerateSingleDouble()
        {
            return ((double)Math.Abs(GetRandomValue())) / ((double)int.MaxValue);
        }
    }
}